package com.itranswarp.weibo.api;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;

final class Utils {

	static final int HTTP_CONNECT_TIMEOUT = 5000;
	static final int HTTP_READ_TIMEOUT = 5000;

	@SuppressWarnings("unchecked")
	public static <T> T httpRequest(HttpMethod httpMethod, String url, String authorization, Map<String, Object> params, boolean returnAsText) throws IOException {
		HttpURLConnection conn = null;
		OutputStream output = null;
		InputStream input = null;
		byte[] httpBody = null;
		String httpUrl = httpMethod==HttpMethod.GET ? url + "?" + urlEncode(params) : url;
		try {
			conn = (HttpURLConnection) new URL(httpUrl).openConnection();
			conn.setConnectTimeout(HTTP_CONNECT_TIMEOUT);
			conn.setReadTimeout(HTTP_READ_TIMEOUT);
			conn.setDoOutput(httpMethod!=HttpMethod.GET);
			conn.setAllowUserInteraction(false);
			conn.setInstanceFollowRedirects(false);
			conn.setRequestMethod(httpMethod==HttpMethod.GET ? "GET" : "POST");
			if (authorization != null)
		        conn.setRequestProperty("Authorization", "OAuth2 " + authorization);
			if (httpMethod == HttpMethod.POST) {
				httpBody = urlEncode(params).getBytes("UTF-8");
			}
			else if (httpMethod == HttpMethod.UPLOAD) {
				String boundary = "----------" + Long.toHexString(System.currentTimeMillis());
				conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
				httpBody = multipartEncode(boundary, params);
			}
			if (httpBody!=null)
				conn.setRequestProperty("Content-Length", String.valueOf(httpBody.length));
			conn.connect();
			if (httpMethod != HttpMethod.GET) {
				output = conn.getOutputStream();
				output.write(httpBody);
				output.flush();
			}
			// read:
			int code = conn.getResponseCode();
			if (code != 200)
				throw new WeiboException("Bad http response: " + code);
			// read json:
			input = conn.getInputStream();
			if (returnAsText) {
				return (T) new String(readContent(input, false), "UTF-8");
			}
			return (T) parseJson(input);
		}
		finally {
			close(output);
			close(input);
			if (conn!=null) {
				conn.disconnect();
			}
		}
	}

	/**
	 *  A sample multipart/form-data; boundary=ABCD:
	 * --ABCD
	 * Content-Disposition: form-data; name="title"
	 * \r\n
	 * Today
	 * --ABCD
	 * Content-Disposition: form-data; name="file"; filename="C:\1.txt"
	 * Content-Type: text/plain
	 * \r\n
	 * <file content of 1.txt>
	 * --ABCD--
	 * \r\n
	 */
	static byte[] multipartEncode(String boundary, Map<String, Object> params) throws IOException {
		byte[] boundaryData = ("--" + boundary).getBytes("UTF-8");
		byte[] crlf = "\r\n".getBytes("UTF-8");
		ByteArrayOutputStream buffer = new ByteArrayOutputStream(1048576); // 1MB
		for (String key : params.keySet()) {
			Object value = params.get(key);
			if (value==null)
				continue;
			buffer.write(boundaryData);
			buffer.write(crlf);
			String filename = null;
			if (value instanceof File) {
				File f = (File) value;
				if ( ! f.isFile() || ! f.canRead())
					throw new WeiboException("Cannot read file: " + f.getPath());
				filename = f.getName();
				value = new BufferedInputStream(new FileInputStream(f));
			}
			if (value instanceof InputStream) {
				InputStream input = (InputStream) value;
				byte[] content = readContent(input, filename!=null);
				buffer.write(("Content-Disposition: form-data; name=\"" + key + "\"; filename=\"" + filename + "\"").getBytes("UTF-8"));
				buffer.write(crlf);
				buffer.write(("Content-Length: " + content.length).getBytes("UTF-8"));
				buffer.write(crlf);
				buffer.write(("Content-Type: " + guessContentType(filename)).getBytes("UTF-8"));
				buffer.write(crlf);
				buffer.write(crlf);
				buffer.write(content);
				buffer.write(crlf);
			}
			else {
				buffer.write(("Content-Disposition: form-data; name=\"" + key + "\"").getBytes("UTF-8"));
				buffer.write(crlf);
				buffer.write(crlf);
				buffer.write(value.toString().getBytes("UTF-8"));
				buffer.write(crlf);
			}
		}
		buffer.write(boundaryData);
		buffer.write("--".getBytes("UTF-8"));
		buffer.write(crlf);
		return buffer.toByteArray();
	}

	static String urlEncode(Map<String, Object> params) throws IOException {
		StringBuilder sb = new StringBuilder(params.size() << 4);
		for (String key : params.keySet()) {
			Object value = params.get(key);
			if (value!=null) {
				sb.append(key).append('=').append(URLEncoder.encode(value.toString(), "UTF-8")).append('&');
			}
		}
		if (sb.length()==0)
			return "";
		return sb.substring(0, sb.length()-1); // remove last '&'
	}

	static byte[] readContent(InputStream input, boolean closeAfterRead) throws IOException {
		ByteArrayOutputStream arrayBuffer = new ByteArrayOutputStream(262144); // 256 K
		byte[] buffer = new byte[65536]; // 64 K
		try {
			int len = input.read(buffer);
			if (len > 0)
				arrayBuffer.write(buffer, 0, len);
		}
		finally {
			if (closeAfterRead)
				close(input);
		}
		return arrayBuffer.toByteArray();
	}

	static void close(Closeable closeable) {
		if (closeable!=null) {
			try {
				closeable.close();
			}
			catch (IOException e) {}
		}
	}

	static Object parseJson(InputStream input) throws IOException {
		Reader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
		return new JsonParser().parseJson(reader);
	}

	static String guessContentType(String filename) {
		String DEFAULT = "application/octet-stream";
		int n = filename.lastIndexOf('.');
		if (n==(-1))
			return DEFAULT;
		String type = mimeMap.get(filename.substring(n));
		return type==null ? DEFAULT : type;
	}

	static void checkError(Object o) {
		if (o instanceof JsonObject) {
			JsonObject jo = (JsonObject) o;
			if (jo.has("errorCode")) {
				int errorCode = jo.get("error_code");
				String error = jo.get("error");
				String request = jo.get("request", "");
				throw new WeiboApiException(errorCode, error, request);
			}
		}
	}

	static final Map<String, String> mimeMap;
	static {
		mimeMap = new HashMap<String, String>();
		mimeMap.put(".png", "image/png");
		mimeMap.put(".gif", "image/gif");
		mimeMap.put(".jpg", "image/jpeg");
		mimeMap.put(".jpe", "image/jpeg");
		mimeMap.put(".jpeg", "image/jpeg");
	}

}
